﻿using System;
using System.Collections.Generic;
using Extensibility;
using System.Runtime.InteropServices;
using Microsoft.Win32;
using System.Windows.Forms;
using System.Reflection;
using Excel = Microsoft.Office.Interop.Excel;
using Word = Microsoft.Office.Interop.Word;
using PowerPoint = Microsoft.Office.Interop.PowerPoint;
using Outlook = Microsoft.Office.Interop.Outlook;
using Office = Microsoft.Office.Core;

namespace SharedAddin
{
    [GuidAttribute("11111111-AF27-4814-9CBE-ED6A39A4B9A5"),
     ProgId("SharedAddin.Connect"),
     ClassInterface(ClassInterfaceType.AutoDual)]
    public class Connect : Object, IDTExtensibility2, Office.ICustomTaskPaneConsumer, Office.IRibbonExtensibility
    {
        /// <summary>
        /// IMPORTANT: Define the application you will use here
        ///            this MUST be static
        /// </summary>
        public static void ThisAddIn_Register()
        {
            office.AddApplication(OfficeBase.APPTYPE.EXCEL, "Excel Shared Addin Example", "Excel Shared Addin Example");
            office.AddApplication(OfficeBase.APPTYPE.WORD, "Word Shared Addin Example", "Word Shared Addin Example");
            office.AddApplication(OfficeBase.APPTYPE.POWERPOINT, "PowerPoint Shared Addin Example", "PowerPoint Shared Addin Example");
            office.AddApplication(OfficeBase.APPTYPE.OUTLOOK, "Outlook Shared Addin Example", "Outlook Shared Addin Example");
        }

        #region Startup/Shutdown
        private void ThisAddIn_Startup(object sender, System.EventArgs e)
        {
            Excel.Application xlApp = office.GetExcel();
            office.GetExcel().WorkbookOpen += (Excel.Workbook Wb) =>
            {
                MessageBox.Show(Wb.Name + " was opened.");
                Marshal.ReleaseComObject(Wb);
            };
        }
        private void ThisAddIn_Shutdown(object sender, System.EventArgs e)
        {
        }
        #endregion

        #region Ribbon Callbacks
        public void Ribbon_Load(Office.IRibbonUI ribbonUI)
        {
            this.Ribbon = ribbonUI;
        }

        public void OnAction(Office.IRibbonControl control)
        {
            if (control.Id == "MyButton")
            {
                showTaskPane();
            }
        }
        #endregion

        private void showTaskPane()
        {
            // create a dynamic control on the fly
            UserControl dynamicControl = new UserControl();
            // add a button to it
            Button btnTest = new Button();
            btnTest.Name = "btnTest";
            btnTest.Text = "Hello";
            // when the user clicks the button on the TaskPane,
            // say hello...
            btnTest.Click += (object sender, EventArgs e) =>
            {
                MessageBox.Show("Hello World!");
            };
            // add the button to the control 
            dynamicControl.Controls.Add(btnTest);
            dynamicControl.Width = 300;
            // now create the taskPane - looks exactly the same
            // as how you would create one in VSTO
            SharedCustomTaskPane myPane = CustomTaskPaneCollection.Add(dynamicControl, "My Pane");
            myPane.VisibleChanged += (object sender, EventArgs e) =>
            {
                // when the taskpane is hidden, invalidate the ribbon
                // so my toggle button can toggle off
                this.Ribbon.Invalidate();
            };
            // dock to the right
            myPane.DockPosition = Office.MsoCTPDockPosition.msoCTPDockPositionRight;
            // show it
            myPane.Visible = true;
        }

        #region RibbonExtensibility
        private Office.IRibbonUI Ribbon;
        string Office.IRibbonExtensibility.GetCustomUI(string RibbonID)
        {
            string xml = null;
            if (office.IsExcelConnected)
            {
                xml = Properties.Resources.ExcelRibbon;
            }
            else if (office.IsWordConnected)
            {
                xml = Properties.Resources.WordRibbon;
            }
            else if (office.IsPowerPointConnected)
            {
                xml = Properties.Resources.PowerPointRibbon;
            }
            else if (office.IsOutlookConnected)
            {
                xml = Properties.Resources.OutlookRibbon;
            }
            return xml;
        }
        #endregion

        #region TaskPaneConsumer
        public SharedCustomTaskPaneCollection CustomTaskPaneCollection;
        void Office.ICustomTaskPaneConsumer.CTPFactoryAvailable(Office.ICTPFactory CTPFactoryInst)
        {
            CustomTaskPaneCollection = new SharedCustomTaskPaneCollection(CTPFactoryInst);
        }
        #endregion

        #region IDTExtensibility2
        static string NAME = "";
        private static OfficeBase office = new OfficeBase();
        private static object ThisAddIn;
        public Connect() { }
        /// <summary>
        /// When we finally do connect and load in Excel we want to get the
        /// reference to the application, so that we can use the application
        /// instace in our UDF as needed
        /// </summary>
        /// <param name="application"></param>
        /// <param name="connectMode"></param>
        /// <param name="addInInst"></param>
        /// <param name="custom"></param>
        public void OnConnection(object application, Extensibility.ext_ConnectMode connectMode, object addInInst, ref System.Array custom)
        {
            // get a reference to the instance of the add-in
            // and set the application instance in out OfficeBase
            ThisAddIn_Register();
            office.SetApplication(application);
            ThisAddIn = addInInst;
            ThisAddIn_Startup(ThisAddIn, new EventArgs());
        }

        /// <summary>
        /// When we disconnect - remove everything - clean up
        /// </summary>
        /// <param name="disconnectMode"></param>
        /// <param name="custom"></param>
        public void OnDisconnection(Extensibility.ext_DisconnectMode disconnectMode, ref System.Array custom)
        {
            // clean up
            if (office.IsExcelConnected)
            {
                Marshal.ReleaseComObject(office.GetExcel());
            }
            if (office.IsPowerPointConnected)
            {
                Marshal.ReleaseComObject(office.GetPowerPoint());
            }
            if (office.IsWordConnected)
            {
                Marshal.ReleaseComObject(office.GetWord());
            }
            if (office.IsOutlookConnected)
            {
                Marshal.ReleaseComObject(office.GetOutlook());
            }
            ThisAddIn = null;
            GC.Collect();
            GC.Collect();
            GC.WaitForPendingFinalizers();
            ThisAddIn_Shutdown(null, new EventArgs());
        }
        // the following functions are required to be defined, but not needed
        public void OnAddInsUpdate(ref System.Array custom) { }
        public void OnStartupComplete(ref System.Array custom) { }
        public void OnBeginShutdown(ref System.Array custom) { }

        /// <summary>
        /// Registers the COM Automation Add-in in the CURRENT USER context
        /// and then registers it in all versions of Excel on the users system
        /// without the need of administrator permissions
        /// </summary>
        /// <param name="type"></param>l
        [ComRegisterFunctionAttribute]
        public static void RegisterFunction(Type type)
        {
            if (office == null)
            {
                office = new OfficeBase();
            }
            ThisAddIn_Register();
            
            string PATH = System.Reflection.Assembly.GetExecutingAssembly().GetName().CodeBase.Replace("\\", "/");
            string ASSM = Assembly.GetExecutingAssembly().FullName;
            int startPos = ASSM.ToLower().IndexOf("version=") + "version=".Length;
            int len = ASSM.ToLower().IndexOf(",", startPos) - startPos;
            string VER = ASSM.Substring(startPos, len);
            string GUID = "{" + type.GUID.ToString().ToUpper() + "}";
            NAME = type.Namespace + "." + type.Name; // global
            string BASE = @"Classes\" + NAME;
            string CLSID = @"Classes\CLSID\" + GUID;

            // open the key
            RegistryKey CU = Registry.CurrentUser.OpenSubKey("Software", true);

            // is this version registred?
            RegistryKey key = CU.OpenSubKey(CLSID + @"\InprocServer32\" + VER);
            if (key == null)
            {
                // The version of this class currently being registered DOES NOT
                // exist in the registry - so we will now register it

                // BASE KEY
                // HKEY_CURRENT_USER\CLASSES\{NAME}
                key = CU.CreateSubKey(BASE);
                key.SetValue("", NAME);

                // HKEY_CURRENT_USER\CLASSES\{NAME}\CLSID}
                key = CU.CreateSubKey(BASE + @"\CLSID");
                key.SetValue("", GUID);

                // CLSID
                // HKEY_CURRENT_USER\CLASSES\CLSID\{GUID}
                key = CU.CreateSubKey(CLSID);
                key.SetValue("", NAME);

                // HKEY_CURRENT_USER\CLASSES\CLSID\{GUID}\Implemented Categories
                key = CU.CreateSubKey(CLSID + @"\Implemented Categories").CreateSubKey("{62C8FE65-4EBB-45e7-B440-6E39B2CDBF29}");

                // HKEY_CURRENT_USER\CLASSES\CLSID\{GUID}\InProcServer32
                key = CU.CreateSubKey(CLSID + @"\InprocServer32");
                key.SetValue("", @"c:\Windows\SysWow64\mscoree.dll");
                key.SetValue("ThreadingModel", "Both");
                key.SetValue("Class", NAME);
                key.SetValue("CodeBase", PATH);
                key.SetValue("Assembly", ASSM);
                key.SetValue("RuntimeVersion", "v4.0.30319");

                // HKEY_CURRENT_USER\CLASSES\CLSID\{GUID}\InProcServer32\{VERSION}
                key = CU.CreateSubKey(CLSID + @"\InprocServer32\" + VER);
                key.SetValue("Class", NAME);
                key.SetValue("CodeBase", PATH);
                key.SetValue("Assembly", ASSM);
                key.SetValue("RuntimeVersion", "v4.0.30319");

                // HKEY_CURRENT_USER\CLASSES\CLSID\{GUID}\ProgId
                key = CU.CreateSubKey(CLSID + @"\ProgId");
                key.SetValue("", NAME);

                foreach(OfficeBase.OfficeAddinInfo info in office.GetAddins())
                {
                    if (info.IsUsed)
                    {
                        key = Registry.CurrentUser.OpenSubKey(@"Software\Microsoft\Office\" + info.App  + @"\Addins", true);
                        if (key != null)
                        {
                            key = key.CreateSubKey(NAME);
                            key.SetValue("Description", info.AddinDescription);
                            key.SetValue("FriendlyName", info.AddinName);
                            key.SetValue("LoadBehavior", 3);
                        }
                    }
                }
            }
        }

        /// <summary>
        /// Unregisters the add-in, by removing all the keys
        /// </summary>
        /// <param name="type"></param>
        [ComUnregisterFunctionAttribute]
        public static void UnregisterFunction(Type type)
        {
            string GUID = "{" + type.GUID.ToString().ToUpper() + "}";
            string NAME = type.Namespace + "." + type.Name;
            string BASE = @"Classes\" + NAME;
            string CLSID = @"Classes\CLSID\" + GUID;
            // open the key
            RegistryKey CU = Registry.CurrentUser.OpenSubKey("Software", true);
            // DELETE BASE KEY
            // HKEY_CURRENT_USER\CLASSES\{NAME}
            try
            {
                CU.DeleteSubKeyTree(BASE);
            }
            catch { }
            // HKEY_CURRENT_USER\CLASSES\{NAME}\CLSID}
            try
            {
                CU.DeleteSubKeyTree(CLSID);
            }
            catch { }

            // now un-register the addin 
            foreach (OfficeBase.OfficeAddinInfo info in office.GetAddins())
            {
                if (info.IsUsed)
                {
                    RegistryKey key = Registry.CurrentUser.OpenSubKey(@"Software\Microsoft\Office\" + info.App + @"\AddIns", true);
                    if (key != null)
                    {
                        try
                        {
                            key.DeleteSubKey(NAME);
                        }
                        catch { }
                    }
                }
            }
        }

        /// <summary>
        /// HELPER FUNCTION
        /// This assists is in determining if the subkey string we are passed
        /// is of the type like:
        ///     8.0
        ///     11.0
        ///     14.0
        ///     15.0
        /// </summary>
        /// <param name="s"></param>
        /// <returns></returns>
        public static bool IsVersionNum(string s)
        {
            int idx = s.IndexOf(".");
            if (idx >= 0 && s.EndsWith("0") && int.Parse(s.Substring(0, idx)) > 0)
                return true;
            else
                return false;
        }
        #endregion

        #region  OfficeBase
        public class OfficeBase
        {
            private Outlook.Application outlook = null;
            private Excel.Application excel = null;
            private Word.Application word = null;
            private PowerPoint.Application ppt = null;

            public bool IsExcelConnected
            {
                get
                {
                    return excel != null;
                }
            }
            public bool IsPowerPointConnected
            {
                get
                {
                    return ppt != null;
                }
            }
            public bool IsWordConnected
            {
                get
                {
                    return word != null;
                }
            }
            public bool IsOutlookConnected
            {
                get
                {
                    return outlook != null;
                }
            }

            private Dictionary<APPTYPE, OfficeAddinInfo> addinInfo = new Dictionary<APPTYPE, OfficeAddinInfo>();
            public List<OfficeAddinInfo> GetAddins()
            {
                List<OfficeAddinInfo> retVal = new List<OfficeAddinInfo>();
                foreach (OfficeAddinInfo info in addinInfo.Values)
                {
                    retVal.Add(info);
                }
                return retVal;
            }

            public enum APPTYPE { EXCEL, POWERPOINT, WORD, OUTLOOK }
            public OfficeBase()
            {
                addinInfo.Add(APPTYPE.EXCEL, new OfficeAddinInfo());
                addinInfo.Add(APPTYPE.WORD, new OfficeAddinInfo());
                addinInfo.Add(APPTYPE.POWERPOINT, new OfficeAddinInfo());
                addinInfo.Add(APPTYPE.OUTLOOK, new OfficeAddinInfo());
            }

            public void AddApplication(APPTYPE type, string name, string description)
            {
                addinInfo[type].App = type.ToString();
                addinInfo[type].AddinName = name;
                addinInfo[type].AddinDescription = description;
                addinInfo[type].IsUsed = true;
            }

            public class OfficeAddinInfo
            {
                public string App { get; set; }
                public string AddinName { get; set; }
                public string AddinDescription { get; set; }
                public bool IsUsed { get; set; }
            }

            public void SetApplication(object application)
            {
                if (application is Excel.Application)
                {
                    excel = application as Excel.Application;
                }
                else if (application is Word.Application)
                {
                    word = application as Word.Application;
                }
                else if (application is PowerPoint.Application)
                {
                    ppt = application as PowerPoint.Application;
                }
                else if (application is Outlook.Application)
                {
                    outlook = application as Outlook.Application;
                }
            }

            public Outlook.Application GetOutlook()
            {
                return outlook;
            }

            public Word.Application GetWord()
            {
                return word;
            }

            public PowerPoint.Application GetPowerPoint()
            {
                return ppt;
            }

            public Excel.Application GetExcel()
            {
                return excel;
            }
        }
        #endregion
    }
}